package wxpay

import (
	"bytes"
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"errors"
	"io/ioutil"
	"net/http"
	"shop/common/log"
	"shop/utils/convertutil"
	"shop/utils/genutil"
	"sort"
	"strconv"
	"strings"
	"time"
)

type Client struct {
	appID    string
	mchID    string
	apiKey   string
	certData struct { // 微信商家tls证书-PEM
		publicKey  []byte
		privateKey []byte
	}
	signType string // 签名类型
}

// NewClient 创建微信支付客户端
// 微信支付开放文档：https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
func NewClient(appID, mchID, apiKey string) *Client {
	return &Client{
		appID:    appID,
		mchID:    mchID,
		apiKey:   apiKey,
		signType: MD5,
	}
}

func (c *Client) SetSignType(signType string) {
	c.signType = signType
}

// SetCertData 设置证书
func (c *Client) SetCertData(certPEMPublicKey, certPEMPrivateKey string) *Client {
	c.certData.publicKey = []byte(certPEMPublicKey)
	c.certData.privateKey = []byte(certPEMPrivateKey)
	return c
}

// 向 params 中添加 appid、mch_id、nonce_str、sign_type、sign
func (c *Client) fillRequestDataAndSign(params map[string]string) map[string]string {
	params["appid"] = c.appID
	params["mch_id"] = c.mchID
	params["nonce_str"] = genutil.RandStr(20)
	params["sign_type"] = c.signType
	params["sign"] = c.Sign(params)
	return params
}

func (c *Client) postXml(url string, params map[string]string, needCert bool) (map[string]string, error) {
	var httpCli *http.Client
	if needCert {
		if c.certData.publicKey == nil || c.certData.privateKey == nil {
			return nil, errors.New("证书数据为空")
		}
		cert, err := tls.X509KeyPair(c.certData.publicKey, c.certData.privateKey)
		if err != nil {
			return nil, err
		}
		httpCli = &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{
					Certificates: []tls.Certificate{cert},
				},
				DisableCompression: true,
			},
		}
	} else {
		httpCli = &http.Client{}
	}

	c.fillRequestDataAndSign(params)
	response, err := httpCli.Post(url, "application/xml; charset=utf-8", strings.NewReader(convertutil.MapToXml(params)))
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()
	arr, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	xmlStr := string(arr)
	log.Debugf("返回结果：%v", xmlStr)
	resp := convertutil.XmlToMap(xmlStr)
	if resp["return_code"] == "SUCCESS" && resp["result_code"] == "SUCCESS" {
		if c.ValidSign(resp) {
			return resp, nil
		} else {
			return nil, errors.New("验签失败")
		}
	}
	return nil, errors.New("request fail: " + resp["return_msg"] + ":" + resp["err_code_des"])
}

// UnifiedOrderParams 微信统一下单参数
type UnifiedOrderParams struct {
	OutTradeNo string // 自己系统的订单号
	TotalFee   int64  // 单位：分
	Body       string // 微信支付订单显示的商品名称，eg：充值100Q币
	TradeType  string // JSAPI,NATIVE,APP
	NotifyUrl  string // 异步回调通知url
	// SpbillCreateIp string // 终端ip，APP支付时需要
	Openid string // JSAPI时必须要, 微信用户在该appid下的openid
}

// UnifiedOrder 微信统一下单
/*
小程序支付，eg:
{
	"appId": "wxa873ca2845a453df",
	"nonceStr": "1618112867",
	"timeStamp": "pxlmOdaAnXCtMn0M",
	"package": "prepay_id=wx22200456595239fa85f9d48fb2686a0000",
	"signType": "MD5",
	"paySign": "13175AF8FB1E1AC50B27BB997DBA935A",
}
二维码支付，eg:
{
	"code_url": "weixin://wxpay/bizpayurl?pr=aWwqldgzz", // 直接到微信聊天消息里或微信app里打开
	"code_base64_img": "...." // 由code_url生成的base64格式的二维码图片
}
*/
func (c *Client) UnifiedOrder(params *UnifiedOrderParams) (map[string]string, error) {
	mapParams := map[string]string{
		"out_trade_no": params.OutTradeNo,
		"total_fee":    strconv.FormatInt(params.TotalFee, 10),
		"body":         params.Body,
		"trade_type":   params.TradeType,
		"notify_url":   params.NotifyUrl,
	}
	if params.TradeType == TradeTypeJsapi {
		mapParams["openid"] = params.Openid
	}
	response, err := c.postXml(UnifiedOrderUrl, mapParams, false)
	if err != nil {
		return nil, err
	}

	// 构造支付需要的参数
	payData := map[string]string{}
	if params.TradeType == TradeTypeNative {
		payData["code_url"] = response["code_url"]
		payData["code_base64_img"] = convertutil.StrToBase64Img(response["code_url"])
		return payData, nil
	} else if params.TradeType == TradeTypeJsapi {
		payData["appId"] = response["appid"]
		payData["package"] = "prepay_id=" + response["prepay_id"]
		payData["timeStamp"] = strconv.FormatInt(time.Now().Unix(), 10)
		payData["nonceStr"] = genutil.RandStr(20)
		payData["signType"] = MD5
		payData["paySign"] = c.Sign(payData)
		return payData, nil
	} else if params.TradeType == TradeTypeApp {
		payData["appid"] = c.mchID
		payData["partnerid"] = c.mchID
		payData["package"] = c.mchID
		payData["prepayid"] = response["prepay_id"]
		payData["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
		payData["noncestr"] = genutil.RandStr(20)
		payData["sign"] = c.Sign(payData)
		return payData, nil
	}
	return nil, errors.New("不支持的交易类型: " + params.TradeType)
}

type OrderQueryParams struct {
	TransactionId string
	OutTradeNo    string // 与TransactionId二选一
}

// OrderQuery 订单查询 transactionId和outOrderNo填一个即可
/* eg:
{
	"appid": "wxa873ca2845a453df",
	"attach": "",
	"bank_type": "OTHERS",
	"cash_fee": "1",
	"cash_fee_type": "CNY",
	"fee_type": "CNY",
	"is_subscribe": "N",
	"mch_id": "1618112867",
	"nonce_str": "Yr9cggE2dCKBf5nS",
	"openid": "ogbA_44fnEu4aNnnieP0XPr_Is_0",
	"out_trade_no": "16401763808821",
	"result_code": "SUCCESS",
	"return_code": "SUCCESS",
	"return_msg": "OK",
	"sign": "8C9F6A796B387D5F3650BECEC4CE5023",
	"time_end": "20211222203606",
	"total_fee": "1",
	"trade_state": "SUCCESS",
	"trade_state_desc": "支付成功",
	"trade_type": "NATIVE",
	"transaction_id": "4200001340202112229083486581"
}
*/
func (c *Client) OrderQuery(params *OrderQueryParams) (map[string]string, error) {
	return c.postXml(OrderQueryUrl, map[string]string{
		"transaction_id": params.TransactionId,
		"out_trade_no":   params.OutTradeNo,
	}, false)
}

type RefundParams struct {
	TransactionId string
	OutTradeNo    string // 与TransactionId二选一
	OutRefundNo   string // 商户系统内部的退款单号，商户系统内部唯一，string(64)
	TotalFee      int64
	RefundFee     int64
	NotifyUrl     string // 填了就覆盖后台配置的
}

// Refund 退款
/*
返回示例：{
	"appid": "wxa873ca2845a453df",
	"cash_fee": "1000",
	"cash_refund_fee": "1",
	"coupon_refund_count": "0",
	"coupon_refund_fee": "0",
	"mch_id": "1618112867",
	"nonce_str": "hrDAizinbY1O1ZAX",
	"out_refund_no": "VePOzaEfcbiJzVrCbSIJQCXNhRqjkc",
	"out_trade_no": "202112251755301",
	"refund_fee": "1",
	"refund_id": "50300800572021122515741794184",
	"result_code": "SUCCESS",
	"return_code": "SUCCESS",
	"return_msg": "OK",
	"sign": "BE530F402C07A5E2BC7AF42F984DAC67",
	"total_fee": "1000",
	"transaction_id": "4200001297202112252341705854"
}
*/
func (c *Client) Refund(params *RefundParams) (map[string]string, error) {
	mapParams := map[string]string{
		"transaction_id": params.TransactionId,
		"out_trade_no":   params.OutTradeNo,
		"out_refund_no":  params.OutRefundNo,
		"total_fee":      strconv.FormatInt(params.TotalFee, 10),
		"refund_fee":     strconv.FormatInt(params.RefundFee, 10),
		"notify_url":     params.NotifyUrl,
	}
	return c.postXml(RefundUrl, c.fillRequestDataAndSign(mapParams), true)
}

// RefundQuery 退款查询，订单号和退款号2选一，订单号可以查该订单的所有退款
/*
返回示例：{
	"appid": "wxa873ca2845a453df",
	"cash_fee": "1000",
	"mch_id": "1618112867",
	"nonce_str": "ELJusuEpyQEbmVBv",
	"out_refund_no_0": "202112251755302",
	"out_refund_no_1": "fsElSpcYRrBnKxOOiHhfdifvLrVuCh",
	"out_refund_no_2": "fatCxsJKBBwTmcUoCEtMbDsTAKKftn",
	"out_refund_no_3": "202112251755301",
	"out_trade_no": "202112251755301",
	"refund_account_0": "REFUND_SOURCE_RECHARGE_FUNDS",
	"refund_account_1": "REFUND_SOURCE_RECHARGE_FUNDS",
	"refund_channel_0": "ORIGINAL",
	"refund_channel_1": "ORIGINAL",
	"refund_count": "2", // 退款次数
	"refund_fee": "2",
	"refund_fee_0": "1",
	"refund_fee_1": "1",
	"refund_id_0": "50300800572021122515733004009",
	"refund_id_1": "50300800572021122515736445297",
	"refund_recv_accout_0": "支付用户零钱",
	"refund_recv_accout_1": "支付用户零钱",
	"refund_status_0": "SUCCESS",
	"refund_status_1": "SUCCESS",
	"refund_success_time_0": "2021-12-25 18:15:23",
	"refund_success_time_1": "2021-12-25 19:01:27",
	"result_code": "SUCCESS",
	"return_msg": "OK",
	"sign": "FE17B32FB165C9937DFEB78768D51279",
	"total_fee": "1000",
	"transaction_id": "4200001297202112252341705854"
}
*/
func (c *Client) RefundQuery(outTradeNo, outRefundNo string) int64 {
	res, err := c.postXml(RefundQueryUrl, map[string]string{
		"out_trade_no":  outTradeNo,
		"out_refund_no": outRefundNo,
	}, false)
	if err != nil {
		log.Errorf("fail to RefundQuery, err: %v", err)
		return 0
	}
	return convertutil.ParseInt64(res["refund_fee"])
}

// CloseOrder 关闭订单
func (c *Client) CloseOrder(params map[string]string) (map[string]string, error) {
	return c.postXml(CloseOrderUrl, params, false)
}

// Reverse 撤销订单
func (c *Client) Reverse(params map[string]string) (map[string]string, error) {
	return c.postXml(ReverseUrl, params, true)
}

func (c *Client) ValidSign(params map[string]string) bool {
	return params["sign"] == c.Sign(params)
}

func (c *Client) Sign(params map[string]string) string {
	var keys = make([]string, 0, len(params))
	for k := range params {
		if k != "" && k != "sign" && params[k] != "" { // 排除空key，空值和sign字段
			keys = append(keys, k)
		}
	}
	sort.Strings(keys) // 给key排序

	var buf bytes.Buffer
	for _, k := range keys {
		buf.WriteString(k)
		buf.WriteString(`=`)
		buf.WriteString(params[k])
		buf.WriteString(`&`)
	}
	buf.WriteString("key=" + c.apiKey) // 密钥
	if c.signType == MD5 {
		dataMd5 := md5.Sum(buf.Bytes())
		return strings.ToUpper(hex.EncodeToString(dataMd5[:]))
	} else if c.signType == HMACSHA256 {
		h := hmac.New(sha256.New, []byte(c.apiKey))
		h.Write(buf.Bytes())
		dataSha256 := h.Sum(nil)
		return strings.ToUpper(hex.EncodeToString(dataSha256[:]))
	}
	return ""
}
